/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "common/napi_helper.h"
#include "controller/lottie_controller_bridge.h"

namespace LottiePro {

NapiHelper::NapiHelper(napi_env env)
{
    env_ = env;
}

napi_env NapiHelper::GetEnv()
{
    return env_;
}

napi_value NapiHelper::Call(napi_value thisObject, napi_value callback,
                            int argsCount, const napi_value *args)
{
    napi_value result = nullptr;
    auto status = napi_call_function(env_, thisObject, callback, argsCount, args, &result);
    this->MaybeRethrowAsCpp(status);
    return result;
}

napi_value NapiHelper::CreateString(std::string const &str)
{
    napi_value result;
    auto status = napi_create_string_utf8(env_, str.c_str(), str.length(), &result);
    this->MaybeThrowFromStatus(status, "Failed to create string");
    return result;
}

napi_value NapiHelper::CreateInteger(int32_t const &value)
{
    napi_value result;
    napi_status status = napi_create_int32(env_, value, &result);
    this->MaybeThrowFromStatus(status, "Failed to create int32");
    return result;
}

napi_value NapiHelper::CreateDouble(double const &value)
{
    napi_value result;
    napi_status status = napi_create_double(env_, value, &result);
    this->MaybeThrowFromStatus(status, "Failed to create double");
    return result;
}

napi_value NapiHelper::CreateArray(std::vector<napi_value> const &value)
{
    napi_status status;
    napi_value result;
    status = napi_create_array(env_, &result);
    this->MaybeThrowFromStatus(status, "Failed to napi_create_array");
    for (size_t i = 0; i < value.size(); ++i) {
        status = napi_set_element(env_, result, static_cast<uint32_t>(i), value[i]);
        this->MaybeThrowFromStatus(status, "Failed to napi_set_element");
    }
    return result;
}

napi_value NapiHelper::CreateBoolean(const bool &value)
{
    napi_status status = napi_ok;
    napi_value result = nullptr;
    status = napi_get_boolean(env_, value, &result);
    this->MaybeThrowFromStatus(status, "Failed to napi_create_boolean");
    return result;
}

napi_value NapiHelper::CreateFunction(std::string const &name, napi_callback callback, void *data)
{
    napi_value result;
    auto status = napi_create_function(env_, name.c_str(), name.length(), callback, data, &result);
    this->MaybeThrowFromStatus(status, "Couldn't create a function");
    return result;
}

napi_ref NapiHelper::CreateReference(napi_value value)
{
    napi_ref result;
    auto status = napi_create_reference(env_, value, 1, &result);
    this->MaybeThrowFromStatus(status, "Couldn't create a reference");
    return result;
}

napi_value NapiHelper::CreateObject()
{
    napi_value result;
    auto status = napi_create_object(env_, &result);
    this->MaybeThrowFromStatus(status, "Couldn't create a Object");
    return result;
}

std::string NapiHelper::GetString(napi_value value)
{
    size_t length = 0;
    napi_status status = napi_ok;
    status = napi_get_value_string_utf8(env_, value, nullptr, 0, &length);
    this->MaybeThrowFromStatus(status, "Failed to get the length of the string");
    if (status != napi_ok) {
        return "";
    }
    std::string buffer(length, '\0');
    status = napi_get_value_string_utf8(env_, value, &buffer[0], length + 1, &length);
    this->MaybeThrowFromStatus(status, "Failed to get the string data");
    return buffer;
}

double NapiHelper::GetDouble(napi_value value)
{
    double result;
    auto status = napi_get_value_double(env_, value, &result);
    this->MaybeThrowFromStatus(status, "Failed to retrieve double value");
    return result;
}

int NapiHelper::GetInteger(napi_value value)
{
    int result;
    auto status = napi_get_value_int32(env_, value, &result);
    this->MaybeThrowFromStatus(status, "Failed to retrieve integer value");
    return result;
}

bool NapiHelper::GetBoolean(napi_value value)
{
    bool result;
    auto status = napi_get_value_bool(env_, value, &result);
    this->MaybeThrowFromStatus(status, "Failed to retrieve boolean value");
    return result;
}

napi_value NapiHelper::GetArrayElement(napi_value array, uint32_t index)
{
    napi_value result;
    auto status = napi_get_element(env_, array, index, &result);
    this->MaybeThrowFromStatus(status, "Failed to retrieve value at index");
    return result;
}

uint32_t NapiHelper::GetArrayLength(napi_value array)
{
    uint32_t length;
    auto status = napi_get_array_length(env_, array, &length);
    this->MaybeThrowFromStatus(status, "Failed to read array length");
    return length;
}

napi_value NapiHelper::GetObjectProperty(napi_value object, std::string const &key)
{
    return GetObjectProperty(object, this->CreateString(key));
}

napi_value NapiHelper::GetObjectProperty(napi_value object, napi_value key)
{
    napi_value result;
    auto status = napi_get_property(env_, object, key, &result);
    this->MaybeThrowFromStatus(status, "Failed to retrieve property from object");
    return result;
}

napi_value NapiHelper::GetReferenceValue(napi_ref ref)
{
    napi_value result;
    auto status = napi_get_reference_value(env_, ref, &result);
    this->MaybeThrowFromStatus(status, "Couldn't get a reference value");
    return result;
}

napi_value NapiHelper::GetGlobal()
{
    napi_value result;
    auto status = napi_get_global(env_, &result);
    this->MaybeThrowFromStatus(status, "Couldn't get the global value");
    return result;
}

ArkUI_NodeContentHandle NapiHelper::GetNodeContentHandle(napi_value value)
{
    ArkUI_NodeContentHandle contentHandle;
    auto status = OH_ArkUI_GetNodeContentFromNapiValue(env_, value, &contentHandle);
    if (status != ARKUI_ERROR_CODE_NO_ERROR) {
        DRAWING_LOGE("NapiHelper::GetNodeContentHandle: Error OH_ArkUI_GetNodeContentFromNapiValue failed");
        return nullptr;
    }
    return contentHandle;
}

std::vector<napi_value> NapiHelper::GetCallbackArgs(napi_callback_info info)
{
    size_t argc;
    napi_get_cb_info(env_, info, &argc, nullptr, nullptr, nullptr);
    return GetCallbackArgs(info, argc);
}

std::vector<napi_value> NapiHelper::GetCallbackArgs(napi_callback_info info, size_t args_count)
{
    size_t argc = args_count;
    std::vector<napi_value> args(args_count, nullptr);
    napi_get_cb_info(env_, info, &argc, args.data(), nullptr, nullptr);
    return args;
}

void NapiHelper::DeleteReference(napi_ref reference)
{
    auto status = napi_delete_reference(env_, reference);
    this->MaybeThrowFromStatus(status, "Couldn't delete a reference");
}

void NapiHelper::addObjectProperty(napi_value object, napi_value key, napi_value value)
{
    auto status = napi_set_property(env_, object, key, value);
    this->MaybeThrowFromStatus(status, "Couldn't add property for object");
}

bool NapiHelper::CheckFuncIsExist(napi_value jsObject, std::string const &funcName)
{
    bool exists = false;
    auto status = napi_has_named_property(env_, jsObject, funcName.c_str(), &exists);
    if (status != napi_ok || !exists) {
        return false;
    }

    napi_value funcValue;
    status = napi_get_named_property(env_, jsObject, funcName.c_str(), &funcValue);
    if (status != napi_ok) {
        return false;
    }

    napi_valuetype type;
    status = napi_typeof(env_, funcValue, &type);
    return (status == napi_ok && type == napi_function);
}

bool NapiHelper::CheckIsArray(napi_value array)
{
    bool result = false;
    auto status = napi_is_array(env_, array, &result);
    if (status != napi_ok) {
        this->MaybeThrowFromStatus(status, "Node-API napi_is_array fail");
        return false;
    }
    return result;
}

bool NapiHelper::CheckPropIsExist(napi_value jsObject, std::string const &propName)
{
    bool result = false;
    auto status = napi_has_named_property(env_, jsObject, propName.c_str(), &result);
    if (status != napi_ok) {
        this->MaybeThrowFromStatus(status, "Node-API napi_has_named_property fail");
        return false;
    }
    return result;
}

bool NapiHelper::CheckIsType(napi_value value, napi_valuetype type)
{
    napi_valuetype valueType;
    auto status = napi_typeof(env_, value, &valueType);
    if (status != napi_ok || valueType != type) {
        this->MaybeThrowFromStatus(status, "Failed to get argument type");
        return false;
    }
    return true;
}

static void MaybeThrowFromStatus(napi_env env, napi_status status, const char *message)
{
    if (status != napi_ok) {
        napi_extended_error_info const *error_info;
        napi_get_last_error_info(env, &error_info);
        std::string msg_str = message;
        std::string error_code_msg_str = ". Error code: ";
        std::string status_str = error_info->error_message;
        std::string full_msg = msg_str + error_code_msg_str + status_str;
        napi_throw_error(env, nullptr, message);
    }
}

void NapiHelper::MaybeThrowFromStatus(napi_status status, const char *message)
{
    LottiePro::MaybeThrowFromStatus(env_, status, message);
}

void NapiHelper::MaybeRethrowAsCpp(napi_status status)
{
    if (status == napi_ok) {
        return;
    }
    bool hasThrown;
    napi_is_exception_pending(env_, &hasThrown);
    if (status != napi_pending_exception && !hasThrown) {
        return;
    }
    napi_value nError;
    napi_get_and_clear_last_exception(env_, &nError);

    auto message = GetObjectProperty(nError, "message");
    auto messageStr = GetString(message);
    throw std::runtime_error(messageStr);
}
} // namespace LottiePro
